﻿using UnityEngine;
using UnityEditor;
using System.Linq;
using System.Collections.Generic;

//------------------------------------------------------------
// shaco Framework
// Copyright © 2017-2020 chang.liu All rights reserved.
// Feedback: 449612236@qq.com
//------------------------------------------------------------

namespace shaco.Instance.ArrayReferenceBind
{
    public class ArrayReferenceBindHierarchy : EditorWindow
    {
        static private List<Component> _currentCopyComponents = null;
        static private System.Type[] _ingoreCopyComponentTypes = new System.Type[] { typeof(MeshRenderer), typeof(MeshFilter), typeof(SkinnedMeshRenderer) };

        [MenuItem("GameObject/shaco/ReferenceBind %&r", false, 2)]
        static void OpenWindow()
        {
            RereshAll();
        }

        [MenuItem("GameObject/shaco/ReferenceBind %&r", true, 2)]
        static bool OpenWindowValid()
        {
            if (null == Selection.gameObjects || 0 == Selection.gameObjects.Length)
                return false;

            foreach (var obj in Selection.gameObjects)
            {
                var allComponent = obj.GetComponents<Component>();
                foreach (var target in allComponent)
                {
                    SerializedObject serializedObject = new SerializedObject(target);
                    var iter = serializedObject.GetIterator();
                    while (iter.NextVisible(true))
                    {
                        if (iter.isArray)
                        {
                            return true;
                        }
                    }
                }
            }
            return false;
        }

        [MenuItem("GameObject/shaco/CopyAllComponent %#&c", false, 4)]
        static void CopyAllComponent()
        {
            var target = Selection.activeGameObject;
            CopyAllComponent(target);
        }

        static public void CopyAllComponent(GameObject target)
        {
            var copyComponents = target.GetComponents<Component>();

            if (null == _currentCopyComponents)
                _currentCopyComponents = new List<Component>();
            _currentCopyComponents.Clear();

            foreach (var iter in copyComponents)
            {
                if (!IsIgnoreComponentType(iter))
                {
                    _currentCopyComponents.Add(iter);
                }
            }

            if (null == _currentCopyComponents || 0 == _currentCopyComponents.Count)
                Debug.Log("No compnent need copy, target=" + target, target);
            else
                Debug.Log("Copy all component, target=" + target + " component count=" + _currentCopyComponents.Count, target);
        }

        [MenuItem("GameObject/shaco/CopyAllComponent %#&c", true, 4)]
        static bool CopyAllComponentValid()
        {
            return null != Selection.activeGameObject && null != Selection.gameObjects && 1 == Selection.gameObjects.Length;
        }

        [MenuItem("GameObject/shaco/PasteAllComponentAsNew %#&n", false, 5)]
        static void PasteAllComponentAsNew()
        {
            var targets = Selection.gameObjects;
            PasteAllComponentAsNew(targets);
        }

        static public void PasteAllComponentAsNew(params GameObject[] targets)
        {
            foreach (var target in targets)
            {
                foreach (var iter in _currentCopyComponents)
                {
                    var newComponent = iter;
                    UnityEditorInternal.ComponentUtility.CopyComponent(newComponent);
                    Component oldComponent = target.GetComponent(newComponent.GetType());
                    if (null != oldComponent)
                        continue;

                    if (!UnityEditorInternal.ComponentUtility.PasteComponentAsNew(target))
                    {
                        Debug.LogError("Paste new component " + newComponent.GetType() + " failed, target=" + target, target);
                    }
                }
                Debug.Log("Paste all component as new, target=" + target + " component count=" + _currentCopyComponents.Count, target);
            }
        }

        [MenuItem("GameObject/shaco/PasteAllComponentAsNew %#&n", true, 6)]
        static bool PasteAllComponentAsNewValid()
        {
            return null != Selection.gameObjects && 0 != Selection.gameObjects.Length && null != _currentCopyComponents && 0 < _currentCopyComponents.Count;
        }

        [MenuItem("GameObject/shaco/PasteAllComponent %#&v")]
        static void PasteAllComponent()
        {
            var targets = Selection.gameObjects;
            PasteAllComponent(targets);
        }

        static public void PasteAllComponent(params GameObject[] targets)
        {
            foreach (var target in targets)
            {
                foreach (var iter in _currentCopyComponents)
                {
                    var newComponent = iter;
                    UnityEditorInternal.ComponentUtility.CopyComponent(newComponent);
                    Component oldComponent = target.GetComponent(newComponent.GetType());
                    if (null == oldComponent)
                        continue;

                    if (!UnityEditorInternal.ComponentUtility.PasteComponentValues(oldComponent))
                    {
                        Debug.LogError("Paste component " + newComponent.GetType() + " failed, target=" + target, target);
                    }
                }
                Debug.Log("Paste all component as new, target=" + target + " component count=" + _currentCopyComponents.Count, target);
            }
        }

        [MenuItem("GameObject/shaco/PasteAllComponent %#&v", true, 6)]
        static bool PasteAllComponentValid()
        {
            return null != Selection.gameObjects && 0 != Selection.gameObjects.Length && null != _currentCopyComponents && 0 < _currentCopyComponents.Count;
        }

        [MenuItem("GameObject/shaco/RemoveAllComponet", false, 7)]
        static void RemoveAllComponent()
        {
            var targets = Selection.gameObjects;
            bool hasRemoved = false;
            foreach (var target in targets)
            {
                var components = target.GetComponents<Component>();
                if (null == components || 0 == components.Length)
                    continue;
                
                bool isRemoveSuccess = false;
                for (int i = components.Length - 1; i >= 0; --i)
                {
                    var iter = components[i];
                    if (iter.GetType() == typeof(Transform))
                        continue;
                        
                    if (!IsIgnoreComponentType(iter))
                    {
                        if (Application.isPlaying)
                            MonoBehaviour.Destroy(iter);
                        else
                            MonoBehaviour.DestroyImmediate(iter);
                        isRemoveSuccess = true;
                    }
                }

                if (isRemoveSuccess)
                {
                    Debug.Log("Remove all component, target=" + target, target);
                    hasRemoved = true;
                }
            }

            if (hasRemoved)
            {
                if (!Application.isPlaying)
                    UnityEditor.SceneManagement.EditorSceneManager.MarkAllScenesDirty();
            }
        }

        [MenuItem("GameObject/shaco/RemoveAllComponent %#&v", true, 7)]
        static bool RemoveAllComponentValid()
        {
            return null != Selection.gameObjects && 0 != Selection.gameObjects.Length;
        }

        static private bool IsIgnoreComponentType(Component com)
        {
            var checkType = com.GetType();
            foreach (var type in _ingoreCopyComponentTypes)
            {
                if (type == checkType)
                {
                    return true;
                }
            }
            return false;
        }

        static private void RereshAll()
        {
            if (null == Selection.gameObjects || 0 == Selection.gameObjects.Length)
                return;

            bool isForceContinue = true;
            foreach (var obj in Selection.gameObjects)
            {
                var allComponent = obj.GetComponents<Component>();
                var childCount = obj.transform.childCount;
                foreach (var iter in allComponent)
                {
                    isForceContinue = RefreshReferencesBind(iter, childCount);
                    if (!isForceContinue)
                        break;
                }

                if (!isForceContinue)
                    break;
            }
        }

        static private bool RefreshReferencesBind(Component target, int childCount)
        {
            if (null == target)
                return true;

            SerializedObject serializedObject = new SerializedObject(target);
            var iter = serializedObject.GetIterator();
            var isForceContinue = true;
            while (iter.NextVisible(true))
            {
                if (iter.isArray)
                {
                    isForceContinue = RefreshListData(target, iter, childCount);
                    if (!isForceContinue)
                        break;
                }
            }
            serializedObject.ApplyModifiedProperties();
            return isForceContinue;
        }

        static private bool RefreshListData(Component target, SerializedProperty list, int childCount)
        {
            if (null == list)
            {
                Debug.LogError("ArrayReferenceBindHierarchy RefreshListData error: list is null");
                return false;
            }

            if (!list.isArray)
            {
                Debug.LogError("ArrayReferenceBindHierarchy RefreshListData error: not a list, target=" + list);
                return false;
            }

            bool isForceContinue = true;
            for (int i = list.arraySize - 1; i >= 0; --i)
            {
                var propertyTmp = list.GetArrayElementAtIndex(i);
                if (propertyTmp.propertyType != SerializedPropertyType.ObjectReference)
                    continue;

                //如果列表中存在asset资源，则需要提示是否强制替换它
                if (AssetDatabase.Contains(propertyTmp.objectReferenceValue))
                {
                    var messsage = string.Format("{0} -> {1}\nThere is a referenced asset. Do you want to continue to force replacement ?", target, propertyTmp.objectReferenceValue);
                    isForceContinue = EditorUtility.DisplayDialog("Warning", messsage, "OK", "Cancel");

                    if (!isForceContinue)
                        EditorGUIUtility.PingObject(target);
                    break;
                }
            }

            if (!isForceContinue)
                return false;

            //偏移数字以第一个参数为准
            var oldFirstElement = list.arraySize > 0 ? list.GetArrayElementAtIndex(0) : null;
            int offsetIndex = 0;
            if (null != oldFirstElement)
            {
                if (oldFirstElement.propertyType == SerializedPropertyType.Integer)
                {
                    offsetIndex = oldFirstElement.intValue;
                }
                else if (oldFirstElement.propertyType == SerializedPropertyType.Float)
                {
                    offsetIndex = (int)oldFirstElement.floatValue;
                }
            }

            int loopCount = null == oldFirstElement || oldFirstElement.propertyType == SerializedPropertyType.ObjectReference ? childCount : System.Math.Max(list.arraySize, 1);

            list.ClearArray();
            for (int i = 0; i < loopCount; ++i)
            {
                var insertIndex = list.arraySize;
                list.InsertArrayElementAtIndex(insertIndex);
                var propertyTmp = list.GetArrayElementAtIndex(insertIndex);

                switch (propertyTmp.propertyType)
                {
                    case SerializedPropertyType.Integer: ChangePropertyInt(propertyTmp, i + offsetIndex); break;
                    case SerializedPropertyType.Float: ChangePropertyFloat(propertyTmp, i + offsetIndex); break;
                    case SerializedPropertyType.ObjectReference: ChangePropertyObjectReference(target.gameObject, propertyTmp, i); break;
                    default: Debug.LogError("unsupport type=" + propertyTmp.propertyType + " name=" + propertyTmp.propertyPath); break;
                }
            }

            return true;
        }

        static private void ChangePropertyInt(SerializedProperty property, int index)
        {
            property.intValue = index;
        }

        static private void ChangePropertyFloat(SerializedProperty property, int index)
        {
            property.floatValue = index;
        }

        static private void ChangePropertyObjectReference(GameObject target, SerializedProperty property, int index)
        {
            if (null == target)
            {
                Debug.LogError("Please select a GameObject in 'Hierachy' Window as parent");
                return;
            }

            if (index < 0 || index > target.transform.childCount - 1) return;

            var childTmp = target.transform.GetChild(index);

            if (property.type.Contains(typeof(GameObject).Name))
                property.objectReferenceValue = childTmp.gameObject;
            else if (property.type.Contains(typeof(Transform).Name))
                property.objectReferenceValue = childTmp.transform;
            else
            {
                var components = childTmp.GetComponents<UnityEngine.Component>();
                for (int i = 0; i < components.Length; ++i)
                {
                    if (components[i] == null) continue;

                    if (property.type.Contains(components[i].GetType().Name))
                    {
                        property.objectReferenceValue = components[i];
                    }
                }
            }
        }
    }
}